/*
 * Engine Alpha ist eine anfängerorientierte 2D-Gaming Engine.
 *
 * Copyright (c) 2011 - 2014 Michael Andonie and contributors.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package ea.internal.phy;

import ea.*;
import ea.internal.util.Logger;

/**
 * Ein <code>Physik</code>-Client, der eine rudimentäre Implementierung <b>Newton'scher Mechanik</b>
 * implementieren soll. Physik-Objekte, die von einem solchen Client betreut werden, zeichnen sich
 * durch folgende Eigenschaften aus:<br /> <ul> <li>Sie haben eine aktuelle
 * </b>Geschwindigkeit</b>.</li> <li>Es wirkt eine <b>Kraft</b> (ggf. stellvertretend als Summe
 * mehrerer Kräfte) auf sie.<li> <li>Sie haben eine <b>Masse</b>.</li> <li>Die Kraft wirkt eine
 * <b>Beschleunigung</b> - also eine <i>Änderung der Geschwindigkeit</i> auf sie aus. Die
 * Beschleunigung hängt ausserdem von der <i>Masse</i> des Objekts ab.</li> <li>Es wirkt konstant
 * <b>Reibung</b>. In diesem Modell bedeutet dies eine <i>dynamische Verminderung der aktuellen
 * Geschwindigkeit</i>. Die Verminderung ist stärker, je höher die Geschwindigkeit ist. Ihre
 * generelle Intensität lässt sich jedoch ändern.</li> <li><b>Neue Kräfte</b> können atomar (also
 * "von jetzt auf gleich", nicht über einen Zeitraum (z.B. 100 ms)) auf Objekte angewendet werden.
 * Das ist Vergleichbar mit einem schnellen Stoß in eine bestimmte Richtung. Dies lässt sich durch
 * einen <b>Impuls</b> realisieren, aber auch über direkte Eingabe einer
 * Geschwindigkeitsänderung.</li> <li>Es können Kräfte <b>dauerhaft</b> auf ein Objekt wirken, wie
 * zum Beispiel die <i>Schwerkraft</i>.</li> <li>Mehrere Objekte können <b>kollidieren</b>. Dann
 * prallen sie <i>elastisch</i> voneinander ab. Dies funktioniert intern über
 * <i>Impulsrechnung</i>.</li> </ul>
 *
 * @author Michael Andonie
 */
@SuppressWarnings ( "serial" )
public class MechanikClient extends PhysikClient implements Ticker {

	/**
	 * Die Grenze d, ab der Vektoren v mit |v|<d auf 0 abgerundet werden.
	 */
	private static float THRESHOLD = 0.00001f;

	/**
	 * Setzt einen neuen Threshold d. Ein Objekt, dass sich mit |v| < d bewegt,
	 * wird angehalten (die Engine sorgt also manuell für v' = 0)
	 * @param threshold	Der Threshold d (in px), ab dem die Engine Geschwindigkeitsvektoren
	 * 					auf 0 setzt.
	 */
	public static void tresholdSetzen(float threshold) {
		THRESHOLD = threshold;
	}
	
	/**
	 * Das Intervall, in dem die Spielmechanik upgedated wird <b>in Sekunden</b>. Wird benutzt für
	 * die Extrapolation. Orientiert sich an der <b>Update-Geschwindigkeit</b> der Zeichenebene
	 *
	 * @see ea.internal.gra.Zeichner#UPDATE_INTERVALL
	 */
	static final float DELTA_T = (float) ea.internal.gra.Zeichner.UPDATE_INTERVALL * 0.001f;

	/**
	 * Der Timer, der sich aller Mechanik-Clients annimmt.
	 */
	public static Manager MECH_TIMER = new Manager();

	/**
	 * Diese Konstante gibt an, wie viele Meter ein Pixel hat. Das ist normalerweise ein sehr
	 * <b>kleiner</b> Wert (Standard: 0.01f).
	 */
	private static float METER_PRO_PIXEL = 0.001f;

	/**
	 * Der Listener zum hoehren von Faellen.
	 */
	private FallReagierbar fallListener = FallDummy.getDummy();

	/**
	 * Das StehReagierbar-Interface, das auf stehen reagieren soll.
	 */
	private StehReagierbar sListener = StehDummy.getDummy();

	/**
	 * Die kritische Tiefe, bei der der Fall-Listener informiert wird.
	 */
	private int kritischeTiefe = 0;

	/**
	 * Die aktuelle Geschwindigkeit v des Client-Objekts.<br /> <b>Einheit: m/s</b>
	 */
	private Vektor velocity;

	/**
	 * Die letzte Geschwindigkeit v des Client-Objekts.<br /> Wird fuer das Absterben der Reibung
	 * benutzt <b>Einheit: m/s</b>
	 */
	private Vektor lastVelocity;

	/**
	 * Die aktuelle Kraft F, die konstant auf das Client-Objekt wirkt. <br /> <b>Einheit: N = m/s^2</b><br />
	 * Konkret z.B. Schwerkraft.
	 */
	private Vektor force;

	/**
	 * Die aktuelle Masse m des Objekts. <br/> <b>Einheit: Kilogramm</b>
	 */
	private float masse = 30.0f;

	/**
	 * Gibt an, ob das Objekt <b>beeinflussbar</b> ist.
	 *
	 * @see #beeinflussbarSetzen(boolean)
	 */
	private boolean beeinflussbar = true;

	/**
	 * Der Luftwiderstandskoeffizient des Objekts ist eine Vereinfachung des
	 * Luftwiderstandsmodells.<br /> F_W = 1/2 * c_W * A * rho * v^2<br /> Heuristik: <br /> F_W =
	 * luftwiderstandskoeffizient * v^2<br />
	 * <p/>
	 * Der Koeffizient ist <b>nichtnegativ</b>.
	 */
	private float luftwiderstandskoeffizient = 40f;

	/**
	 * Gibt an, wie viel Energie beim Aufprall gegen dieses Objekt (als nicht beeinflussbares Objekt)
	 * erhalten bleibt.
	 * 1 ~= 100%
	 * 0 ~=   0%
	 */
	private float elastizitaet = 0.34f;

	/**
	 * Der Collider für schnelle und effiziente Praekollisionstests.
	 */
	private KreisCollider collider;

	/**
	 * Konstruktor erstellt einen neuen Mechanik-Client.
	 *
	 * @param ziel
	 * 		das Ziel-Objekt für diesen Client.
	 */
	public MechanikClient (Raum ziel) {
		super(ziel);
		collider = ziel.dimension().umschliessenderKreis();
		einfluesseZuruecksetzen();
		MECH_TIMER.anmelden(this, ea.internal.gra.Zeichner.UPDATE_INTERVALL);
		CollisionHandling.anmelden(this);
		
	}

	/**
	 * Setzt, wie viele Meter auf einen Pixel im Spielfenster gehen.
	 *
	 * @param meterpropixel
	 * 		Die Anzahl an Metern, die auf einen Pixel fallen.<br/> Beispiele:<br /> <ul>
	 * 		<li><code>10(.0f)</code> => Auf einen Pixel fallen <b>10</b> Meter. => Ein Meter = 0,1
	 * 		Pixel</li> <li><code>0.1f</code> => Auf einen Pixel fallen <b>0,1</b> Meter. => Ein Meter =
	 * 		10 Pixel</li> </ul>
	 */
	public static void setzeMeterProPixel (float meterpropixel) {
		if (meterpropixel <= 0.0f) {
			throw new IllegalArgumentException("Die Anzahl an Metern pro Pixel muss positiv sein!");
		} else if (MECH_TIMER.hatAktiveTicker()) {
			throw new RuntimeException("Die Anzahl von Metern pro Pixel kann nach der Nutzung der " + "Physik nicht mehr geändert werden!");
		}
		METER_PRO_PIXEL = meterpropixel;
	}

	/**
	 * @return the velocity
	 */
	public Vektor getVelocity () {
		return velocity;
	}

	/**
	 * In der <code>tick()</code>-Methode des Mechanik-Clients wird die <b>diskrete
	 * Weiterrechnung</b> der verschiedenen Parameter realisiert sowie die Anwendung der
	 * Geschwindigkeit auf die aktuelle Position des Client-Objekts. Dies ist vergleichbar mit der
	 * <i>Methode der kleinen Schritte</i> aus der Physik.
	 */
	@Override
	public void tick () {
		//Kraftaenderung -> Kraft_aktuell = Kraft + Luftwiderstand
		//Luftwiderstand = 1/2 * c_W * A * rho * v^2
		//Heuristik: luftwiderstandskoeffizient * v^2
		Vektor momentanekraft = force.summe(velocity.gegenrichtung().multiplizieren((luftwiderstandskoeffizient * velocity.laenge())));

		//Beschleunigungsbestimmung -> a = F / m

		//Delta v bestimmen -> delta v = a * delta t = F * (delta t / m)
		//v_neu = v_alt + delta v
		velocity = velocity.summe(momentanekraft.multiplizieren(DELTA_T / masse));

		//Delta s bestimmen -> delta s = v_neu * delta t + [1/2 * a_neu * (delta t)^2]
		// =~= v_neu * delta t  [heuristik]
		//bewegen um delta s
		bewegen(velocity.multiplizieren(DELTA_T).teilen(METER_PRO_PIXEL));
		//System.out.println("Move:" + velocity.multiplizieren(DELTA_T));

		//Critical Depth:
		if (ziel.positionY() > kritischeTiefe) fallListener.fallReagieren();

		//Genügend für Ende? -> Heuristik: |v| < d [mit d geschickt gewählt]
		Vektor dif = velocity.differenz(lastVelocity);
		if (dif.manhattanLength() < THRESHOLD && dif.manhattanLength() != 0) {
			System.out.println("T");
			velocity = Vektor.NULLVEKTOR;
		}

		//Update: Lasvelocity für den nächsten Step ist die aktuelle
		lastVelocity = velocity;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean bewegen (Vektor v) {
		ziel.verschieben(v);
		collider.verschieben(v);
		return false;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void aufloesen () {
		CollisionHandling.abmelden(this);
	}

	/**
	 * {@inheritDoc} Löst einen Impulssprung aus. Nur aus Kompatibilitätsgründen vorhanden.
	 *
	 * @return always <code>true</code>.
	 */
	@Override
	@Deprecated
	public boolean sprung (int kraft) {
		this.impulsHinzunehmen(new Vektor(60, 0));
		return true;
	}

	/**
	 * {@inheritDoc} Aktiviert / Deaktiviert eine Standardschwerkraft. Nur aus
	 * Kompatibilitätsgründen vorhanden.
	 */
	@Override
	@Deprecated
	public void schwerkraftAktivSetzen (boolean aktiv) {
		force = aktiv ? new Vektor(0, 10) : Vektor.NULLVEKTOR;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void kritischeTiefeSetzen (int tiefe) {
		this.kritischeTiefe = tiefe;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void fallReagierbarAnmelden (FallReagierbar f, int tiefe) {
		this.fallListener = f;
		this.kritischeTiefeSetzen(tiefe);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void stehReagierbarAnmelden (StehReagierbar s) {
		this.sListener = s;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean steht () {
		// TODO Auto-generated method stub
		return false;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	@Deprecated
	public void schwerkraftSetzen (int schwerkraft) {
		this.kraftSetzen(new Vektor(0, 0.01f * schwerkraft));
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void impulsHinzunehmen (Vektor impuls) {
		//Grundrechnung:
		//p + delta p = m * v_neu
		//(m * v_alt) + delta p = m * v_neu
		//v_neu = v_alt + ([delta p] / m)
		this.velocity = velocity.summe(impuls.teilen(masse));
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void geschwindigkeitHinzunehmen (Vektor geschwindigkeit) {
		//v_neu = v_alt + delta v
		this.velocity = velocity.summe(geschwindigkeit);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public float getLuftwiderstandskoeffizient () {
		return luftwiderstandskoeffizient;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean istBeeinflussbar () {
		return beeinflussbar;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public float getMasse () {
		return masse;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Vektor getForce () {
		return force;
	}
	
	/**
	 * @return Die Elastizitaet des Objekts.
	 */
	public float getElastizitaet() {
		return elastizitaet;
	}

	/**
	 * Setzt die Elastizität für dieses Objekt neu. Hat nur einen Effekt, wenn 
	 * dieses Objekt nicht beeinflussbar ist.
	 * @param elastizitaet Die Elastizität dieses Objekts in %. 1 = Voller Energieerhalt
	 *  					 --- 0 = Voller Energieverlust
	 */
	public void setElastizitaet(float elastizitaet) {
		if(elastizitaet < 0) {
			Logger.error("Die Elastizität eines Objekts kann nicht negativ sein. Die Eingabe war " + elastizitaet + " .");
			return;
		}
		this.elastizitaet = elastizitaet;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void luftwiderstandskoeffizientSetzen (float luftwiderstandskoeffizient) {
		if (luftwiderstandskoeffizient < 0) {
			throw new IllegalArgumentException("Der Luftwiderstandskoeffizient darf nicht negativ sein! Eingabe war " +
					luftwiderstandskoeffizient + ".");
		}
		this.luftwiderstandskoeffizient = luftwiderstandskoeffizient;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void beeinflussbarSetzen (boolean beeinflussbar) {
		this.beeinflussbar = beeinflussbar;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void masseSetzen (float masse) {
		this.masse = masse;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void kraftSetzen (Vektor kraft) {
		this.force = kraft;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void geschwindigkeitSetzen (Vektor geschwindigkeit) {
		this.velocity = geschwindigkeit;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void einfluesseZuruecksetzen () {
		force = Vektor.NULLVEKTOR;
		velocity = Vektor.NULLVEKTOR;
		lastVelocity = Vektor.NULLVEKTOR;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void kraftAnwenden (Vektor kraft, float t_kraftuebertrag) {
		//es gilt in dieser Heuristik: p = F * t_kraftübertrag
		//=>p = kraft * t_kraftübertrag
		//=> Impuls p anwenden.
		impulsHinzunehmen(kraft.multiplizieren(t_kraftuebertrag));
	}

	/**
	 * Gibt den Collider zurück.
	 *
	 * @return Der Collider des Elements.
	 */
	public KreisCollider collider () {
		return collider;
	}
}
